﻿namespace Util
{
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Security.Cryptography;
    using System.Text;
    using Microsoft.VisualBasic.FileIO;

    class FileHashData : IComparable<FileHashData>
    {
        public bool IsRef { get { return isRef; } }
        public FileInfo Info { get { return info; } }

        long size;
        string md5;
        FileInfo info;
        bool isRef;

        public FileHashData(long s, string m, FileInfo i, bool r)
        {
            size = s;
            md5 = m;
            info = i;
            isRef = r;
        }

        public int SameWith(FileHashData f)
        {
            if (size != f.size) return size.CompareTo(f.size);
            if (md5 != f.md5) return md5.CompareTo(f.md5);
            return 0;
        }

        public int CompareTo(FileHashData f)
        {
            int res = SameWith(f);
            if (res != 0) return res;
            if (isRef != f.isRef) return isRef.CompareTo(f.isRef);
            return 0;
        }
    }

    public static class FileManager
    {
        #region For each file

        public delegate void FileHandler(FileInfo f, params object[] args);

        /// <summary>
        /// 
        /// </summary>
        /// <param name="src"></param>
        /// <param name="handler"></param>
        /// <param name="args"></param>
        public static void ForEachFile(DirectoryInfo src, FileHandler handler, params object[] args)
        {
            foreach (FileInfo f in src.GetFiles())
            {
                handler(f, args);
            }
            foreach (DirectoryInfo s in src.GetDirectories())
            {
                ForEachFile(s, handler, args);
            }
        }

        /// <summary>
        /// Useless
        /// </summary>
        /// <param name="f"></param>
        public static void HandlerDelete(FileInfo f, params object[] args)
        {
            File.Delete(f.FullName);
        }

        /// <summary>
        /// Useless
        /// </summary>
        /// <param name="f"></param>
        public static void HandlerRecycledDelete(FileInfo f, params object[] args)
        {
            FileSystem.DeleteFile(f.FullName, UIOption.OnlyErrorDialogs, RecycleOption.SendToRecycleBin);
        }

        #endregion

        #region For each file dir to dir transfer

        public delegate void FileTransferHandler(FileInfo f, DirectoryInfo dst, params object[] args);

        /// <summary>
        /// For each file f in @src, do some operation on it using @handler with the help of @dst directory.
        /// When traveling a directory @s, we create a corresponding directory @d according to the structure of @s.
        /// </summary>
        /// <param name="src"></param>
        /// <param name="handler"></param>
        /// <param name="args"></param>
        public static void ForEachFileDirToDirTransfer(DirectoryInfo src, DirectoryInfo dst, FileTransferHandler handler, params object[] args)
        {
            foreach (FileInfo f in src.GetFiles())
            {
                handler(f, dst, args);
            }
            foreach (DirectoryInfo s in src.GetDirectories())
            {
                DirectoryInfo d = Directory.CreateDirectory(Path.Combine(dst.FullName, s.Name));
                ForEachFileDirToDirTransfer(s, d, handler, args);
            }
        }

        #endregion

        #region Find duplicates

        private static void HandlerGetInfo(FileInfo f, params object[] args)
        {
            using (FileStream fs = new FileStream(f.FullName, FileMode.Open, FileAccess.Read))
            {
                MD5 calculator = MD5.Create();
                Byte[] buffer = calculator.ComputeHash(fs);
                calculator.Clear();

                StringBuilder builder = new StringBuilder();
                for (int i = 0; i < buffer.Length; i++)
                {
                    builder.Append(buffer[i].ToString("x2"));
                }
                ((List<FileHashData>)args[0]).Add(new FileHashData(f.Length, builder.ToString(), f, (bool)(args[1])));
            }
        }

        public static List<FileInfo> FindDuplicates(DirectoryInfo referenceDir, DirectoryInfo dstDir)
        {
            List<FileHashData> list = new List<FileHashData>();
            ForEachFile(referenceDir, HandlerGetInfo, list, true);
            ForEachFile(dstDir, HandlerGetInfo, list, false);
            list.Sort();

            List<FileInfo> duplicates = new List<FileInfo>();
            for (int i = 1; i < list.Count; ++i)
            {
                if (list[i].SameWith(list[i - 1]) == 0 && !list[i].IsRef)
                {
                    duplicates.Add(list[i].Info);
                }
            }
            return duplicates;
        }

        #endregion
    }
}
